﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using MessageServer.utilities;
using MessageServer.config;
using MessageServer.channels;
using System.Threading;

namespace MessageServer.channels.socket
{
    /**
     * This class contains the configuration possible for a socket channel.
     * */
    public class SocketChannelConfig : Config
    {
        private bool _reuseaddr;            //reuseaddr 
        private bool _keepalive;            //time to keep the connection alive
        private int _sendTimeout;           //timeout for sending data
        private int _receiveTimeout;        //timeout for receiving data
        private int _receiveBufferSize;     //the buffer size for receiving data
        private int _sendBufferSize;        //the buffer size for sending data

        private int _port;                  //port where to listen for connections
        private PipeLineFactory _factory = null;    //The factory for the client pipeline. The client conns will have a different pipeline from server

        public bool reuseaddress { get { return _reuseaddr;  } set { _reuseaddr = value; } }
        public bool keepalive { get { return _keepalive; } set { _keepalive = value; } }
        public int sendtimeout { get { return _sendTimeout; } set { _sendTimeout = value; } }
        public int receiveTimeout { get { return _receiveTimeout; } set { _receiveTimeout = value; } }
        public int sendBufferSize { get { return _sendBufferSize; } set { _sendBufferSize = value; } }
        public int receiveBufferSize { get { return _receiveBufferSize; } set { _receiveBufferSize = value; } }
        public int port { get { return _port; } set { _port = value; } }

        public Properties options { get { return null; } set { } }
        public PipeLineFactory clientpipefactory { get { return _factory; } set { _factory = value; } }


        /**
         * Helper function to setup the socket based on this configuration.
         * */
        internal void setupSocket(Socket socket)
        {
            ChannelLogger.LOG.LogDebug("SocketChannelConfig:setupSocket:setting up Socket for configuration.");
            socket.ReceiveTimeout = receiveTimeout;
            socket.SendTimeout = sendtimeout;
            socket.ReceiveBufferSize = receiveBufferSize;
            socket.SendBufferSize = sendBufferSize;
            socket.Blocking = true;
        }
    }

    /**
     * Factory to create a serverchannel.
     * */
    public class ServerFactory : ChannelFactory
    {
        SocketChannelConfig _config = null;     //The configuration using which the channel is created.

        /**
         * Constructor to create a channel based on configuration 
         * passed to the factory.
         * */
        public ServerFactory(SocketChannelConfig conf)
        {
            _config = conf;
        }

        /**
         * Creates a server channel which streams messages up and down the
         * pipe parameter passed to this function.
         * */
        public Channel createChannel(ChannelPipeline pipe)
        {
            ChannelLogger.LOG.LogDebug("ServerFactory:createChannel:Creating new ServerChannel");
            return new ServerChannel(_config, pipe);
        }
    }

    /**
     * A server channel that accepts and creates AcceptedChannel for each connection
     * coming into the server.
     * */
    public class ServerChannel : AbstractChannel
    {
        private SocketChannelConfig _config;        //config based on which the server is created
        protected ChannelPipeline _pipe;            //the pipe through which data is streamed up and down
        protected ServerSocket _socket;             //the serversocket used to create this channel

        private bool _readonly;                     //if this server is a readonly
        private bool _writeonly;                    //if this server is a writeonly

        public override Config config { get { return _config; } set { throw new NotImplementedException();  } }
        public override ChannelPipeline pipeline { get { return _pipe; } set { _pipe = value; } }
        public override bool connected { get { return _socket.socket.Connected; } }
        public override bool bound { get { return _socket.socket.IsBound; } }
        public override bool readable { get { return _readonly; } set { _readonly = value; } }
        public override bool writable { get { return _writeonly; } set { _writeonly = value; } }
        public override EndPoint localaddress { get { return _socket.socket.LocalEndPoint; } }
        public override EndPoint remoteaddress { get { return _socket.socket.RemoteEndPoint; } }

        /**
         * Attribute that exposes the serversocket
         * */
        public ServerSocket server { get { return _socket; } }

        /**
         * Creates a new ServerChannel. This cannot be called directly. Use the correct factory
         * to create this. The channel is created for the given config and pipe to stream data.
         * */
        internal ServerChannel(SocketChannelConfig config, ChannelPipeline pipe) : base(1)
        {
            _config = config; //cannot change it once constructed
            _pipe = pipe;
            _socket = new ServerSocket();
            ChannelUtils.fireOpenEvent(this);
        }

        /**
         * Closes the current server socket and fires a close event.
         * */
        public override EventTracker close()
        {
            ChannelLogger.LOG.LogDebug("ServerChannel:close:closing the server channel");
            _socket.close();
            return ChannelUtils.fireCloseEvent(this, null);
        }
    }

    internal class DummyBuffer : ByteBuffer
    {
        internal DummyBuffer() : base(10)
        {
            String val = "DUMMY";
            System.Text.UTF8Encoding enc = new System.Text.UTF8Encoding();
            Append(enc.GetBytes(val), 0, val.Length);
        }
    }

    /**
     * Event listener for the close event, so that the socket can be closed
     * at the end of it.
     * */
    class CloseDone : EventStateListener
    {
        AcceptedChannel _channel;       //channel on which close is fired.

        //can be constructed only within this namespace
        internal CloseDone(AcceptedChannel channel)
        {
            _channel = channel;
        }

        /**
         * This closes the clientsocket irrespective of whether
         * handlers succeeded.
         * */
        public void statechanged(EventTracker state)
        {
//            _channel.clientsocket.close();
        }
    }

    /*
     * This class is created when new connections are accepted by the serverchannel.
     * */
    class AcceptedChannel : AbstractChannel
    {
        ClientSocket _socket;           //the socket created 
        ChannelPipeline _pipe;          //the pipe through which all events should pass
        SocketChannelConfig _config;    //config for this socket
        TSList<ByteBuffer> _dataToWrite; //The data to be written to the channel

        /**
         * Creates a channel for the accepted socket and the config
         * **/
        public AcceptedChannel(ClientSocket client, SocketChannelConfig props, ChannelPipeline pipe, int id) : base(id)
        {
            ChannelLogger.LOG.LogDebug("AcceptedChannel:AcceptedChannel:accepted a socket:" + id);
            client.channelid = id;
            _socket = client;
            if (_socket != null)
            {
                _socket.attachment = this;
                _pipe = pipe;
                _config = props;
                if (props != null) props.setupSocket(_socket.socket);
                _dataToWrite = new TSList<ByteBuffer>(new DummyBuffer());
            }
            else
                throw new Exception("Error Socket cannot be null");
        }

        //Attribute to expose the socket of the accepted socket
        public Socket socket { get { return _socket.socket; } }

        //Attribute to expose the client socket of the accepted socket
        public ClientSocket clientsocket { get { return _socket; } }
        public override Config config { get { return _config; } set { throw new NotImplementedException(); } }
        public override ChannelPipeline pipeline { get { return _pipe; } set { _pipe = value; } }
        public override bool connected { get { return _socket.socket.Connected; } }
        public override EndPoint remoteaddress { get { return _socket.socket.RemoteEndPoint; } }

        public override EventTracker close()
        {
            //close it here and remove it from the select.
            _socket.close();
            CloseDone close = new CloseDone(this);
            EventTracker track = ChannelUtils.fireCloseEvent(this, close);
            //wait till done. Since this is critical
            //track.waittofinish(1000);
            return track;
        }

        /**
         * Queue the data to be written to this channel. Will be written when the socket becomes
         * available to be written to.
         * */
        public void queueData(ByteBuffer buffer)
        {
            ChannelLogger.LOG.LogDebug("Queueing Data: " + buffer.Length + " in " + channelid);
            _dataToWrite.push(buffer);
            _socket.writePresent = true; //flag it to indicate need to write.
        }

        /**
         * Returns the next data to be written into the channel.
         * */
        public ByteBuffer nextInQueue()
        {
            ByteBuffer buffer = _dataToWrite.pop();
            if (buffer is DummyBuffer) { _socket.writePresent = false; return null; }
            return buffer;
        }

    }
}
